<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Keybox Selector</title>

<link href="https://fonts.googleapis.com/css2?family=Material+Icons" rel="stylesheet">

<style>
:root{
  --bg:#081018;
  --fg:#eaf6ff;
  --muted:#97aac0;
  --accent:#2aa8ff;

  --col-aosp:#ffb347;
  --col-private:#d28bff;

  --glass:rgba(255,255,255,0.04);
}

html,body{
  margin:0;padding:0;background:var(--bg);color:var(--fg);
  font-family:Inter,Roboto,system-ui;height:100%;
}
.container{max-width:1050px;margin:12px auto;padding:14px;}
.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;}
.title{font-size:20px;font-weight:700;color:var(--accent);}
.note{font-size:13px;color:var(--muted);}
.controls{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;}
input,select,button{
  padding:8px 10px;border-radius:10px;background:transparent;
  color:var(--fg);border:1px solid var(--glass);
}
button.primary{background:var(--accent);color:#001;border:none;cursor:pointer;}
button.primary,button.danger{width:80px;text-align:center;}

.pkg{font-weight:700;font-size:90%;cursor:pointer;}

.pkg-aosp{color:var(--col-aosp);}
.pkg-private{color:var(--col-private);}
.pkg-default{color:var(--fg);}

table{width:100%;border-collapse:collapse;}
th,td{padding:10px;border-bottom:1px solid rgba(255,255,255,0.06);text-align:left;}

.segment{
  display:inline-flex;border-radius:20px;overflow:hidden;
  border:1px solid rgba(255,255,255,0.06);
}
.segment button{
  background:transparent;border:none;padding:6px 12px;
  cursor:pointer;color:var(--muted);
}
.segment button.active{
  background:linear-gradient(180deg,var(--accent),#0ea1ff);
  color:#001;font-weight:600;
}

.modeSel{margin-left:8px;padding:6px;}

.loadMore{
  display:block;margin:10px auto;padding:8px 12px;border-radius:8px;
  border:1px solid var(--glass);background:transparent;cursor:pointer;
}

.material-icons{vertical-align:middle;}

.selected-row{background:rgba(255,255,255,0.05);}
</style>
</head>

<body>
<div class="container">

  <div class="header">
    <div>
      <div class="title"><span class="material-icons">vpn_key</span> Keybox Selector</div>
      <div class="note">Powered by Integrity Box</div>
    </div>
    <div style="display:flex;gap:8px;">
      <button id="btnBackup" class="primary"><span class="material-icons">upload</span> Backup</button>
      <button id="btnRestore" class="danger"><span class="material-icons">history</span> Restore</button>
      <button id="btnSave" class="primary"><span class="material-icons">save</span> Save</button>
    </div>
  </div>

  <div class="controls">
    <input id="filter" placeholder="Filter package…" style="flex:1">
    <select id="bulkKeybox">
      <option value="default">Default</option>
      <option value="aosp">AOSP</option>
      <option value="private">Private</option>
    </select>
    <select id="bulkMode">
      <option value="auto">AUTO</option>
      <option value="generate">GENERATE</option>
      <option value="leaf">LEAF</option>
    </select>
    <button id="btnAddSelected" class="primary"><span class="material-icons">add</span> Add</button>
    <button id="btnRemoveSelected" class="danger"><span class="material-icons">remove</span> Remove</button>
    <div id="previewButtons" style="display:flex;gap:6px;flex-wrap:wrap;"></div>
  </div>

  <table>
    <thead>
      <tr>
        <th></th>
        <th id="pkgHeader" style="cursor:pointer;">Package (click to select all)</th>
        <th>Keybox / Mode</th>
      </tr>
    </thead>
    <tbody id="appsBody"><tr><td colspan="3">Loading…</td></tr></tbody>
  </table>

  <button id="btnLoadMore" class="loadMore" style="display:none">
    <span class="material-icons">more_horiz</span> Load More
  </button>

</div>

<script>
// KernelSU toast popup
function popup(msg,type="info"){
  try{
    if(typeof window.toast==="function"){window.toast(String(msg));return;}
    if(window.kernelsu && typeof window.kernelsu.toast==="function"){window.kernelsu.toast(String(msg));return;}
    if(typeof ksu==="object" && typeof ksu.toast==="function"){ksu.toast(String(msg));return;}
  }catch{}
}

// CORE SHELL + HELPERS
async function runShell(cmd){
  return new Promise((res, rej) => {
    const execTarget =
      window.parent?.ksu?.exec ? window.parent.ksu :
      window.ksu?.exec        ? window.ksu :
      null;

    if (!execTarget || typeof execTarget.exec !== "function") {
      rej("ksu.exec not available");
      return;
    }

    const cb = "cb_" + Date.now() + "_" + (Math.random()*9999|0);

    (window.parent || window)[cb] = (c, o, e) => {
      delete (window.parent || window)[cb];
      if (c === 0) res((o || "").replace(/\r/g, ""));
      else rej(e || o || "error");
    };

    execTarget.exec(cmd, "{}", cb);
  });
}

function esc(s){
  return "'"+s.replace(/'/g,"'\\''")+"'"
}

const TARGET="/data/adb/tricky_store/target.txt";
const TARGET_BAK="/sdcard/tee_simulator_target.txt.bak";
const KEY_AOSP="aosp_keybox.xml";
const KEY_PRIVATE="secondary_keybox.xml";

let installedApps=[];
let visibleApps=[];
let targetMap={"__GLOBAL__":[]};
let selectedSet=new Set();
let renderIndex=0;
const PAGE=80;

async function readLines(p){
  try{
    const out=await runShell(`if [ -f ${esc(p)} ]; then cat ${esc(p)}; fi`);
    return out.split("\n").map(x=>x.trim()).filter(Boolean);
  }catch{return [];}
}

async function writeLines(p,lines){
  try{
    const q=lines.map(l=>esc(l)).join(" ");
    await runShell(`mkdir -p $(dirname ${esc(p)}) && printf "%s\\n" ${q} > ${esc(p)}`);
    return true;
  }catch{return false;}
}

function parsePm(out){
  return out.split("\n")
    .map(l=>l.trim())
    .filter(Boolean)
    .map(l=>({pkg:l.replace("package:","")}));
}

async function fetchApps(){
  try{
    const out=await runShell("pm list packages -3 || pm list packages || true");
    return parsePm(out);
  }catch{return [];}
}

function parseTarget(lines){
  const map={"__GLOBAL__":[]};
  let cur="__GLOBAL__";
  for(const l of lines){
    if(l.startsWith("[") && l.endsWith("]")){
      cur=l.slice(1,-1);
      map[cur]=map[cur]||[];
      continue;
    }
    map[cur].push(l);
  }
  return map;
}

function base(r){
  return r.replace(/[!?]$/,"");
}

function detectMode(r){
  if(r.endsWith("!")) return "generate";
  if(r.endsWith("?")) return "leaf";
  return "auto";
}

function findMapping(pkg){
  for(const sec in targetMap){
    for(const raw of targetMap[sec]){
      if(base(raw)===pkg)
        return{sec,raw};
    }
  }
  return null;
}

// ROW CREATION + CLICK-TO-SELECT
function createRow(app){
  const tr=document.createElement("tr");
  tr.dataset.pkg=app.pkg;

  tr.innerHTML=`
    <td></td> 
    <td class="pkg pkg-default" data-pkg="${app.pkg}">${app.pkg}</td>
    <td>
      <div class="segment" data-pkg="${app.pkg}">
        <button data-k="default"><span class="material-icons">apps</span></button>
        <button data-k="aosp"><span class="material-icons">android</span></button>
        <button data-k="private"><span class="material-icons">lock</span></button>
      </div>

      <select class="modeSel" data-pkg="${app.pkg}">
        <option value="auto">AUTO</option>
        <option value="generate">GENERATE</option>
        <option value="leaf">LEAF</option>
      </select>
    </td>
  `;

  tr.querySelector(".pkg").onclick = () => toggleSelect(app.pkg);

  return tr;
}

// SELECT HANDLING
function toggleSelect(pkg){
  if(selectedSet.has(pkg)) selectedSet.delete(pkg);
  else selectedSet.add(pkg);
  applySelectionStyles();
}

function applySelectionStyles(){
  document.querySelectorAll("#appsBody tr").forEach(tr=>{
    const pkg=tr.dataset.pkg;
    tr.classList.toggle("selected-row", selectedSet.has(pkg));
  });
}

pkgHeader.onclick = () => {
  if(selectedSet.size !== visibleApps.length){
    visibleApps.forEach(a => selectedSet.add(a.pkg));
  } else {
    selectedSet.clear();
  }
  applySelectionStyles();
  popup(`Mapped ${pkg} → ${b.dataset.k.toUpperCase()}`);
};

// TABLE RENDERING
function clearTable(){
  appsBody.innerHTML="";
  renderIndex=0;
}

function renderApps(list){
  visibleApps=list;
  clearTable();
  appendChunk();
}

function appendChunk(){
  const frag=document.createDocumentFragment();
  const end=Math.min(renderIndex+PAGE, visibleApps.length);

  for(let i=renderIndex;i<end;i++){
    frag.appendChild(createRow(visibleApps[i]));
  }

  appsBody.appendChild(frag);
  renderIndex=end;
  btnLoadMore.style.display = renderIndex < visibleApps.length ? "block" : "none";
  refreshRowBindings();
  applySelectionStyles();
}

btnLoadMore.onclick = appendChunk;

// COLORING PKG NAME BASED ON KEYBOX
function applyPkgColor(pkg){
  const pkgCell = document.querySelector(`.pkg[data-pkg="${pkg}"]`);
  if(!pkgCell) return;

  const map=findMapping(pkg);
  if(!map){
    pkgCell.className="pkg pkg-default";
    return;
  }

  if(map.sec===KEY_AOSP){
    pkgCell.className="pkg pkg-aosp";
  } else if(map.sec===KEY_PRIVATE){
    pkgCell.className="pkg pkg-private";
  } else {
    pkgCell.className="pkg pkg-default";
  }
}

// PREVENT DUPLICATES, CLEAN BEFORE ANY ADD
function removePkgEverywhere(pkg){
  for(const s in targetMap){
    targetMap[s] = targetMap[s].filter(r => base(r)!==pkg);
  }
}

// KEYBOX + BUTTON BINDINGS
function refreshRowBindings(){

  document.querySelectorAll(".segment").forEach(seg=>{
    const pkg=seg.dataset.pkg;
    const btns=seg.querySelectorAll("button");

    // initialize active states based on targetMap
    const map=findMapping(pkg);
    if(map){
      const secName = 
        map.sec==="__GLOBAL__" ? "default" :
        map.sec===KEY_AOSP    ? "aosp" :
        "private";

      btns.forEach(b => b.classList.toggle("active", b.dataset.k===secName));
      const modeSel=document.querySelector(`.modeSel[data-pkg="${pkg}"]`);
      modeSel.value=detectMode(map.raw);
    }

    /* KEYBOX BUTTON CLICK */
    btns.forEach(b=>{
      b.onclick = () => {
        const key=b.dataset.k;

        btns.forEach(x=>x.classList.remove("active"));
        b.classList.add("active");

        removePkgEverywhere(pkg);

        const sec =
          key==="default" ? "__GLOBAL__" :
          key==="aosp"    ? KEY_AOSP :
                            KEY_PRIVATE;

        const modeSel=document.querySelector(`.modeSel[data-pkg="${pkg}"]`);
        const mode=modeSel.value;
        const raw =
          mode==="generate"? pkg+"!" :
          mode==="leaf"?     pkg+"?" :
                           pkg;

        targetMap[sec] = targetMap[sec]||[];
        targetMap[sec].push(raw);

        applyPkgColor(pkg);

        popup(`Mapped ${pkg} → ${b.dataset.k.toUpperCase()}`);
      };
    });

    /* MODE CHANGE HANDLER */
    const modeSel=document.querySelector(`.modeSel[data-pkg="${pkg}"]`);
    modeSel.onchange = () => {
      const activeBtn = seg.querySelector("button.active");
      if(!activeBtn) return;

      const sec =
        activeBtn.dataset.k==="default" ? "__GLOBAL__" :
        activeBtn.dataset.k==="aosp"    ? KEY_AOSP :
                                          KEY_PRIVATE;

      removePkgEverywhere(pkg);

      const mode=modeSel.value;
      const raw =
        mode==="generate"? pkg+"!" :
        mode==="leaf"?     pkg+"?" :
                           pkg;

      targetMap[sec] = targetMap[sec]||[];
      targetMap[sec].push(raw);

      applyPkgColor(pkg);
    };
  });
}

// BULK OPERATIONS
document.getElementById("btnAddSelected").onclick = async () => {
  const sel = [...selectedSet];
  if(sel.length===0) return;

  const kb=document.getElementById("bulkKeybox").value;
  const mode=document.getElementById("bulkMode").value;

  const sec =
    kb==="default" ? "__GLOBAL__" :
    kb==="aosp"    ? KEY_AOSP :
                     KEY_PRIVATE;

  for(const pkg of sel){
    removePkgEverywhere(pkg);
  }

  targetMap[sec] = targetMap[sec] || [];

  sel.forEach(pkg=>{
    const raw =
      mode==="generate"? pkg+"!" :
      mode==="leaf"?     pkg+"?" :
                       pkg;
    targetMap[sec].push(raw);
    applyPkgColor(pkg);
  });

  await persist();
  await refresh();
};

document.getElementById("btnRemoveSelected").onclick = async () => {
  const sel=[...selectedSet];
  sel.forEach(pkg=>removePkgEverywhere(pkg));
  await persist();
  await refresh();
};

// SAVE
async function persist(){
  const out=[];

  if(targetMap["__GLOBAL__"]?.length){
    targetMap["__GLOBAL__"].forEach(x=>out.push(x));
    out.push("");
  }

  if(targetMap[KEY_AOSP]?.length){
    out.push(`[${KEY_AOSP}]`, ...targetMap[KEY_AOSP], "");
  }
  if(targetMap[KEY_PRIVATE]?.length){
    out.push(`[${KEY_PRIVATE}]`, ...targetMap[KEY_PRIVATE], "");
  }

  // trim ending blanks
  while(out.length && out[out.length-1]==="") out.pop();

  await writeLines(TARGET, out);
}

document.getElementById("btnSave").onclick = async () => {
  await persist();
  popup("Saved.");
};

// BACKUP & RESTORE
document.getElementById("btnBackup").onclick = async () => {
  try {
    await runShell(`cp ${esc(TARGET)} ${esc(TARGET_BAK)} 2>/dev/null || true`);
    popup("Backup created successfully!");
  } catch(e){
    popup("Backup failed: " + e);
  }
};

document.getElementById("btnRestore").onclick = async () => {
  try {
    await runShell(`cp ${esc(TARGET_BAK)} ${esc(TARGET)} 2>/dev/null || true`);
    await refresh();
    popup("Restored from backup.");
  } catch(e){
    popup("Restore failed: " + e);
  }
};

// FILTERING
let debounceTimer;
document.getElementById("filter").oninput = () => {
  clearTimeout(debounceTimer);
  debounceTimer=setTimeout(()=>{
    const q = filter.value.toLowerCase();
    const list = installedApps.filter(a=>a.pkg.toLowerCase().includes(q));
    renderApps(list);
  }, 300);
};

// REFRESH
async function refresh(){
  selectedSet.clear();
  appsBody.innerHTML = `<tr><td colspan="3">Loading…</td></tr>`;

  installedApps = await fetchApps();
  const lines = await readLines(TARGET);
  targetMap = parseTarget(lines);

  renderApps(installedApps);

  // apply color for pkgs
  installedApps.forEach(a=>applyPkgColor(a.pkg));
}

// INITIAL LOAD
(async()=>{
  await refresh();
})();
</script>

</body>
</html>
